/**
 * 
 * Copyright 2009, 2010 Mike Jamer [email: mikejamer@gmail.com ]
 * 
 * This File is part of Moose IRC
 * 
 * MooseIRC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * MooseIRC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MooseIRC.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using moose.irc;

namespace moose.extension
{
	/// <summary>
	/// Extension Manager manages the loading of MooseIRC Engine Extensions,
	/// Additionally, it manages the creation and organizing of Extension Actions etc.
	/// </summary>
	static internal class ExtensionManager
	{

		/// <summary>
		/// Cached command type information
		/// </summary>
		private static Dictionary<ActionTypes, Dictionary<string, Type>> m_action_typecache;

		/// <summary>
		/// Action Extensions Type cache
		/// </summary>
		private static Dictionary<Type, Type> m_extensions_typecache;

		/// <summary>
		/// Cached instances of commands 
		/// </summary>
		private static Dictionary<ActionTypes, Dictionary<string, IrcAction>> m_actions;

		/// <summary>
		/// Initializes Static Members 
		/// </summary>
		static ExtensionManager ()
		{
			m_action_typecache = new Dictionary<ActionTypes, Dictionary<string, Type>> ();
            m_action_typecache[ActionTypes.Command] = new Dictionary<string, Type>();
            m_action_typecache[ActionTypes.Response] = new Dictionary<string, Type>();

			m_actions = new Dictionary<ActionTypes, Dictionary<string, IrcAction>> ();
            m_actions[ActionTypes.Command] = new Dictionary<string, IrcAction>();
            m_actions[ActionTypes.Response] = new Dictionary<string, IrcAction>();
			
            m_extensions_typecache = new Dictionary<Type, Type> ();
            load_extensions();
		}

		/// <summary>
		/// Attempt to get an action, specify the type, and specify 
		/// the trigger text, to locate an appropriate action. 
		/// </summary>
		/// <param name="type">
		/// A <see cref="ActionTypes"/>
		/// </param>
		/// <param name="trigger">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// Instance of an action, or null if we just dont have an action like this
		/// </returns>
		public static IrcAction get_action (ActionTypes type, string trigger)
		{
            IrcAction action = null;
            if (m_actions.ContainsKey(type) && m_actions[type].ContainsKey(trigger))
                action = m_actions[type][trigger];
			//This will be null if it has not yet been created
			if (action != null)
				return action;
			//So.. if it's not null, make like a bandit..
			//Otherwise, try and create one
			if (m_action_typecache[type].ContainsKey (trigger)) {
				Type action_type = m_action_typecache[type][trigger];
				action = (IrcAction)action_type.Assembly.CreateInstance (action_type.ToString ());
				m_actions[type][trigger] = action;
				return m_actions[type][trigger];
			} else {
				//If no definition exists, we just cant do it..
				return null;
			}
		}

		/// <summary>
		/// Get Irc Action Extension
		/// </summary>
		/// <param name="type">
		/// A <see cref="ActionTypes"/>
		/// </param>
		/// <param name="base_action_type">
		/// A <see cref="Type"/>
		/// </param>
		/// <returns>
		/// Instance of an action, or null if we just dont have an action like this
		/// </returns>
		public static IrcAction get_extension (Type base_action_type, IrcAction base_action)
		{
			if (m_extensions_typecache.ContainsKey (base_action_type)) {
				Type extension_type = m_extensions_typecache[base_action_type];
                m_extensions_typecache.Remove(base_action_type); //We shouldnt need this any more, as we've already extended that action
				return (IrcAction)extension_type.Assembly.CreateInstance(
				                                                         extension_type.ToString(), 
				                                                         false,
				                                                         BindingFlags.Default, 
				                                                         null, 
				                                                         new object[] { base_action },
																		 null, null);
			} else {
				return null;
			}
		}

		/// <summary>
		/// Loads Extensions,
		/// loads each assembly, and scans through types, then caches each type
		/// that is useful to us.
		/// </summary>
		private static void load_extensions ()
		{
			Assembly asm = null;
			foreach (string assembly_path in Directory.GetFiles (Environment.CurrentDirectory, "*.dll")) {
				try {
					asm = Assembly.LoadFile (assembly_path);
				} catch (Exception caught) {
					Console.Error.Write (caught.Message);
					Console.Error.Write ("");
					Console.Error.Write (caught.StackTrace);
					Console.Error.WriteLine ();
					continue;
				}
				if (asm == null)
					continue;
				
				load_extension_actions (asm);
				
			}
			return;
		}

		/// <summary>
		/// Load Extension Actions, 
		/// to simplify this method, I've handed the Action Type in as a parameter,
		/// this will mean that this method will be called twice, once as a Command, and
		/// once as a Response
		/// </summary>
		/// <param name="extension">
		/// A <see cref="Assembly"/>
		/// The actual extension assembly
		/// </param>
		/// <param name="type">
		/// A <see cref="ActionTypes"/>
		/// Action Type
		/// </param>
		private static void load_extension_actions (Assembly extension)
		{
			object[] attributes = null;
			foreach (Type action in extension.GetTypes ()) {
				attributes = action.GetCustomAttributes (typeof(ActionAttribute), false);
				if (attributes.Length == 1) {
					//There can be only one action attribute
					cache_extension_action (action);
					//Cache it
				}
			}
			return;
		}

		/// <summary>
		/// Cache a specific Action Type 
		/// </summary>
		/// <param name="action">
		/// A <see cref="Type"/>
		/// </param>
		/// <param name="action_attribute">
		/// A <see cref="ActionAttribute"/>
		/// </param>
		private static void cache_extension_action (Type action)
		{
			object[] attributes = action.GetCustomAttributes (typeof(ActionAttribute), false);
			ActionAttribute action_attribute = (ActionAttribute)attributes[0];

			//I'm not going to check this, because I'm only calling it from one checked place...
			foreach (object trigger in action.GetCustomAttributes (typeof(TriggerAttribute), false))
				m_action_typecache[action_attribute.Type][((TriggerAttribute)trigger).TriggerText] = action;

			foreach (object extends in action.GetCustomAttributes (typeof(ExtendsAttribute), false))
				m_extensions_typecache[((ExtendsAttribute)extends).BaseType] = action;
			return;
		}
		
	}
}
